#pragma once

#include "dfa_constructor.hpp"
#include "dfa_group_manager.hpp"
#include "log.hpp"
#include <vector>

namespace dfa
{
	class DfaMinMachine
	{
	public:
	    DfaMinMachine(DfaConstructor* dfa_constructor)
            : _dfa_constructor(dfa_constructor) 
            , _dfa_group_manager(new DfaGroupManager())
            , _new_group(nullptr)
            , _add_new_group(false)
            , _dfa_list(_dfa_constructor->getDfaList())
            , _dfa_trans_table(_dfa_constructor->getDfaTransTable())
        {}

        auto minimize()
        {
            addNoAcceptingDfaToGroup();
            addAcceptingDfaToGroup();

            do {
                _add_new_group = false;
                doGroupSepOnNum();
                doGroupSepOnChar();
            }while (_add_new_group);

            createMinDfaTransTable();
            return _min_dfa;
        }

	private:
        void doGroupSepOnNum()
        {
            for (int i = 0; i < _dfa_group_manager->getGroupCount(); i++) {
                int dfa_count = 1;
                _new_group = nullptr;
                auto group = _dfa_group_manager->getGroupAt(i);
                auto first = group->getDfaAt(0);
                auto next = group->getDfaAt(dfa_count);
                
                while (next) {
                    for (char c = '0'; c < '0' + 9; ++c) 
                        if (doGroupSepOnInput(group, first, next, c)) {
                            _add_new_group = true;
                            break;
                        }
                    next = group->getDfaAt(++dfa_count);
                }
                group->commitRemove();
            }
        }

        void doGroupSepOnChar()
        {
            for (int i = 0; i < _dfa_group_manager->getGroupCount(); ++i) {
                int dfa_count = 1;
                _new_group = nullptr;
                auto group = _dfa_group_manager->getGroupAt(i);
                auto first = group->getDfaAt(0);
                auto next = group->getDfaAt(dfa_count);
                
                while (next) {
                    for (uint8_t c = 0; c < ASCII_COUNT; ++c) 
                        if ((c < '0' || c > '9') && doGroupSepOnInput(group, first, next, c)) {
                            _add_new_group = true;
                            break;
                        }
                    next = group->getDfaAt(++dfa_count);
                }
                group->commitRemove();
            }
        }

        bool doGroupSepOnInput(DfaGroup* group, Dfa* first, Dfa* next, char c)
        {
            auto goto_first = _dfa_trans_table[first->getStateNum()][c];
            auto goto_next = _dfa_trans_table[next->getStateNum()][c];
            if (_dfa_group_manager->getContainingGroup(goto_first) == _dfa_group_manager->getContainingGroup(goto_next)) return false;

            if (!_new_group) _new_group = _dfa_group_manager->createNewGroup();

            group->toRemove(next);
            _new_group->add(next);
            log::logMessage(log::LOG_LEVEL::_DEBUG, "Dfa: %d and Dfa: %d jump to different group on input char %c", first->getStateNum(), next->getStateNum(), c);
            log::logMessage(log::LOG_LEVEL::_DEBUG, "remove Dfa: %d from group: %d", next->getStateNum(), group->getGroupNum());
            return true;
        }

        void createMinDfaTransTable()
        {
            _dfa_constructor->initTransTable(_min_dfa);
            for (auto& e : _dfa_list) {
                auto from = e->getStateNum();
                for (uint8_t c = 0; c < ASCII_COUNT; ++c) {
                    auto to = _dfa_trans_table[from][c];
                    if (to == STATE_FAILURE) continue;
                    auto from_group = _dfa_group_manager->getContainingGroup(from);
                    auto to_group = _dfa_group_manager->getContainingGroup(to);
                    _min_dfa[from_group->getGroupNum()][c] = to_group->getGroupNum();
                }
            }
        }

        void addNoAcceptingDfaToGroup()
        {
            auto group = _dfa_group_manager->createNewGroup();
            for (auto dfa : _dfa_list) 
                if (!dfa->getAccepted()) group->add(dfa);
        }

        void addAcceptingDfaToGroup()
        {
            auto group = _dfa_group_manager->createNewGroup();
            for (auto dfa : _dfa_list) 
                if (dfa->getAccepted()) group->add(dfa);
        }

	    DfaConstructor* _dfa_constructor; 
        DfaGroupManager* _dfa_group_manager;
        std::vector<std::vector<int>> _dfa_trans_table;
        std::vector<std::vector<int>> _min_dfa;
        std::vector<Dfa*> _dfa_list;
        DfaGroup* _new_group;
        bool _add_new_group;
	};
} /* namespace  */
